<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="../../aosa.css" type="text/css">
    <title>500 Lines or Less: Web Spreadsheet</title>
  </head>
  <body>

    <div class="titlebox">
      <h1>500 Lines or Less<br>Web Spreadsheet</h1>
      <p class="author">Audrey Tang</p>
    </div>

    <p><em>A self-educated programmer and translator, Audrey works with Apple as an independent contractor on cloud service localization and natural language technologies. Audrey has previously designed and led the first working Perl 6 implementation, and served in computer language design committees for Haskell, Perl 5, and Perl 6. Currently Audrey is a full-time g0v contributor and leads Taiwan’s first e-Rulemaking project.</em></p>

<p>This chapter introduces a web spreadsheet written in 99 lines of the three languages natively supported by web browsers: HTML, JavaScript, and CSS.</p>

<p>The ES5 version of this project is available as a <a href="http://jsfiddle.net/audreyt/LtDyP/">jsFiddle</a>.</p>

<p><em>(This chapter is also available in <a href="https://github.com/aosabook/500lines/blob/master/spreadsheet/spreadsheet.zh-tw.markdown">Traditional Chinese</a>)</em>.</p>

<h2 id="introduction">Introduction</h2>

<p>When Tim Berners-Lee invented the web in 1990, <em>web pages</em> were written in HTML by marking up text with angle-bracketed <em>tags</em>, assigning a logical structure to the content. Text marked up within <code>&lt;a&gt;…&lt;/a&gt;</code> became <em>hyperlinks</em> that would refer the user to other pages on the web.</p>

<p>In the 1990s, browsers added various presentational tags to the HTML vocabulary, including some notoriously nonstandard tags such as <code>&lt;blink&gt;…&lt;/blink&gt;</code> from Netscape Navigator and <code>&lt;marquee&gt;…&lt;/marquee&gt;</code> from Internet Explorer, causing widespread problems in usability and browser compatibility.</p>

<p>In order to restrict HTML to its original purpose—describing a document’s logical structure—browser makers eventually agreed to support two additional languages: CSS to describe presentational styles of a page, and JavaScript (JS) to describe its dynamic interactions.</p>

<p>Since then, the three languages have become more concise and powerful through twenty years of co-evolution. In particular, improvements in JS engines made it practical to deploy large-scale JS frameworks, such as <a href="http://angularjs.org/">AngularJS</a>.</p>

<p>Today, cross-platform <em>web applications</em> (such as web spreadsheets) are as ubiquitous and popular as platform-specific applications (such as VisiCalc, Lotus 1-2-3 and Excel) from the previous century.</p>

<p>How many features can a web application offer in 99 lines with AngularJS? Let’s see it in action!</p>

<h2 id="overview">Overview</h2>

<p>The <a href="https://github.com/audreyt/500lines/tree/master/spreadsheet/code">spreadsheet</a> directory contains our showcase for late-2014 editions of the three web languages: <a href="http://www.w3.org/TR/html5/">HTML5</a> for structure, <a href="http://www.w3.org/TR/css3-ui/">CSS3</a> for presentation, and the JS <a href="http://git.io/es6features">ES6 “Harmony”</a> standard for interaction. It also uses <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/webstorage.html">web storage</a> for data persistence and <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/workers.html">web workers</a> for running JS code in the background. As of this writing, these web standards are supported by Firefox, Chrome, and Internet Explorer 11+, as well as mobile browsers on iOS 5+ and Android 4+.</p>

<p>Now let’s open <a href="http://audreyt.github.io/500lines/spreadsheet/">our spreadsheet</a> in a browser (<a href="#figure-19.1">Figure 19.1</a>):</p>

<div class="center figure">
<a name="figure-19.1"></a><img src="spreadsheet-images/01-initial.png" alt="Figure 19.1 - Initial Screen" title="Figure 19.1 - Initial Screen" />
</div>

<p class="center figcaption">
<small>Figure 19.1 - Initial Screen</small>
</p>

<h3 id="basic-concepts">Basic Concepts</h3>

<p>The spreadsheet spans two dimensions, with <em>columns</em> starting from <strong>A</strong>, and <em>rows</em> starting from <strong>1</strong>. Each <em>cell</em> has a unique <em>coordinate</em> (such as <strong>A1</strong>) and <em>content</em> (such as &quot;1874&quot;), which belongs to one of four <em>types</em>:</p>

<ul>
<li>Text: &quot;+&quot; in <strong>B1</strong> and &quot;-&gt;&quot; in <strong>D1</strong>, aligned to the left.</li>
<li>Number: &quot;1874&quot; in <strong>A1</strong> and &quot;2046&quot; in <strong>C1</strong>, aligned to the right.</li>
<li>Formula: <code>=A1+C1</code> in <strong>E1</strong>, which <em>calculates</em> to the <em>value</em> &quot;3920&quot;, displayed with a light blue background.</li>
<li>Empty: All cells in row <strong>2</strong> are currently empty.</li>
</ul>

<p>Click &quot;3920&quot; to set <em>focus</em> on <strong>E1</strong>, revealing its formula in an <em>input box</em> (<a href="#figure-19.2">Figure 19.2</a>).</p>

<div class="center figure">
<a name="figure-19.2"></a><img src="spreadsheet-images/02-input.png" alt="Figure 19.2 - Input Box" title="Figure 19.2 - Input Box" />
</div>

<p class="center figcaption">
<small>Figure 19.2 - Input Box</small>
</p>

<p>Now let’s set focus on <strong>A1</strong> and <em>change</em> its content to &quot;1&quot;, causing <strong>E1</strong> to <em>recalculate</em> its value to &quot;2047&quot; (<a href="#figure-19.3">Figure 19.3</a>).</p>

<div class="center figure">
<a name="figure-19.3"></a><img src="spreadsheet-images/03-changed.png" alt="Figure 19.3 - Changed Content" title="Figure 19.3 - Changed Content" />
</div>

<p class="center figcaption">
<small>Figure 19.3 - Changed Content</small>
</p>

<p>Press <strong>ENTER</strong> to set focus to <strong>A2</strong> and change its content to <code>=Date()</code>, then press <strong>TAB</strong>, change the content of <strong>B2</strong> to <code>=alert()</code>, then press <strong>TAB</strong> again to set focus to <code>C2</code> (<a href="#figure-19.4">Figure 19.4</a>).</p>

<div class="center figure">
<a name="figure-19.4"></a><img src="spreadsheet-images/04-error.png" alt="Figure 19.4 - Formula Error" title="Figure 19.4 - Formula Error" />
</div>

<p class="center figcaption">
<small>Figure 19.4 - Formula Error</small>
</p>

<p>This shows that a formula may calculate to a number (&quot;2047&quot; in <strong>E1</strong>), a text (the current time in <strong>A2</strong>, aligned to the left), or an <em>error</em> (red letters in <strong>B2</strong>, aligned to the center).</p>

<p>Next, let’s try entering <code>=for(;;){}</code>, the JS code for an infinite loop that never terminates. The spreadsheet will prevent this by automatically <em>restoring</em> the content of <strong>C2</strong> after an attempted change.</p>

<p>Now reload the page in the browser with <strong>Ctrl-R</strong> or <strong>Cmd-R</strong> to verify that the spreadsheet content is <em>persistent</em>, staying the same across browser sessions. To <em>reset</em> the spreadsheet to its original contents, press the 'curved arrow' button on the top-left corner.</p>

<h3 id="progressive-enhancement">Progressive Enhancement</h3>

<p>Before we dive into the 99 lines of code, it’s worthwhile to disable JS in the browser, reload the page, and note the differences (<a href="#figure-19.5">Figure 19.5</a>).</p>

<ul>
<li>Instead of a large grid, only a 2x2 table remains onscreen, with a single content cell.</li>
<li>Row and column labels are replaced by <code>{{ row }}</code> and <code>{{ col }}</code>.</li>
<li>Pressing the reset button produces no effect.</li>
<li>Pressing <strong>TAB</strong> or clicking into the first line of content still reveals an editable input box.</li>
</ul>

<div class="center figure">
<a name="figure-19.5"></a><img src="spreadsheet-images/05-nojs.png" alt="Figure 19.5 - With JavaScript Disabled" title="Figure 19.5 - With JavaScript Disabled" />
</div>

<p class="center figcaption">
<small>Figure 19.5 - With JavaScript Disabled</small>
</p>

<p>When we disable the dynamic interactions (JS), the content structure (HTML) and the presentational styles (CSS) remain in effect. If a website is useful with both JS and CSS disabled, we say it adheres to the <em>progressive enhancement</em> principle, making its content accessible to the largest audience possible.</p>

<p>Because our spreadsheet is a web application with no server-side code, we must rely on JS to provide the required logic. However, it does work correctly when CSS is not fully supported, such as with screen readers and text-mode browsers.</p>

<div class="center figure">
<a name="figure-19.6"></a><img src="spreadsheet-images/06-nocss.png" alt="Figure 19.6 - With CSS Disabled" title="Figure 19.6 - With CSS Disabled" />
</div>

<p class="center figcaption">
<small>Figure 19.6 - With CSS Disabled</small>
</p>

<p>As shown in <a href="#figure-19.6">Figure 19.6</a>, if we enable JS in the browser and disable CSS instead, the effects are:</p>

<ul>
<li>All background and foreground colors are gone.</li>
<li>The input box and the cell value are both displayed, instead of just one at a time.</li>
<li>Otherwise, the application still works the same as the full version.</li>
</ul>

<h2 id="code-walkthrough">Code Walkthrough</h2>

<p><a href="#figure-19.7">Figure 19.7</a> shows the links between HTML and JS components. In order to make sense of the diagram, let’s go through the four source code files, in the same sequence as the browser loads them.</p>

<div class="center figure">
<a name="figure-19.7"></a><img src="spreadsheet-images/00-architecture.png" alt="Figure 19.7 - Architecture Diagram" title="Figure 19.7 - Architecture Diagram" />
</div>

<p class="center figcaption">
<small>Figure 19.7 - Architecture Diagram</small>
</p>

<ul>
<li><strong>index.html</strong>: 19 lines</li>
<li><strong>main.js</strong>: 38 lines (excluding comments and blank lines)</li>
<li><strong>worker.js</strong>: 30 lines (excluding comments and blank lines)</li>
<li><strong>styles.css</strong>: 12 lines</li>
</ul>

<h3 id="html">HTML</h3>

<p>The first line in <code>index.html</code> declares that it’s written in HTML5 with the UTF-8 encoding:</p>

<pre class="sourceCode html"><code class="sourceCode html"><span class="dt">&lt;!DOCTYPE </span>html<span class="dt">&gt;</span><span class="kw">&lt;html&gt;&lt;head&gt;&lt;meta</span><span class="ot"> charset=</span><span class="st">&quot;UTF-8&quot;</span><span class="kw">&gt;</span></code></pre>

<p>Without the <code>charset</code> declaration, the browser may display the reset button’s Unicode symbol as <code>â†»</code>, an example of <em>mojibake</em>: garbled text caused by decoding issues.</p>

<p>The next three lines are JS declarations, placed within the <code>head</code> section as usual:</p>

<pre class="sourceCode html"><code class="sourceCode html">  <span class="kw">&lt;script</span><span class="ot"> src=</span><span class="st">&quot;lib/angular.js&quot;</span><span class="kw">&gt;&lt;/script&gt;</span>
  <span class="kw">&lt;script</span><span class="ot"> src=</span><span class="st">&quot;main.js&quot;</span><span class="kw">&gt;&lt;/script&gt;</span>
  <span class="kw">&lt;script&gt;</span>
      <span class="kw">try</span> { <span class="ot">angular</span>.<span class="fu">module</span>(<span class="st">&#39;500lines&#39;</span>) }
      <span class="kw">catch</span>(e){ location=<span class="st">&quot;es5/index.html&quot;</span> }
  <span class="kw">&lt;/script&gt;</span></code></pre>

<p>The <code>&lt;script src=&quot;…&quot;&gt;</code> tags load JS resources from the same path as the HTML page. For example, if the current URL is <code>http://abc.com/x/index.html</code>, then <code>lib/angular.js</code> refers to <code>http://abc.com/x/lib/angular.js</code>.</p>

<p>The <code>try{ angular.module('500lines') }</code> line tests if <code>main.js</code> is loaded correctly; if not, it tells the browser to navigate to <code>es5/index.html</code> instead. This <em>redirect-based graceful degradation</em> technique ensures that for pre-2015 browsers with no ES6 support, we can use the translated-to-ES5 versions of JS programs as a fallback.</p>

<p>The next two lines load the CSS resource, close the <code>head</code> section, and begin the <code>body</code> section containing the user-visible part:</p>

<pre class="sourceCode html"><code class="sourceCode html">  <span class="kw">&lt;link</span><span class="ot"> href=</span><span class="st">&quot;styles.css&quot;</span><span class="ot"> rel=</span><span class="st">&quot;stylesheet&quot;</span><span class="kw">&gt;</span>
<span class="kw">&lt;/head&gt;&lt;body</span><span class="ot"> ng-app=</span><span class="st">&quot;500lines&quot;</span><span class="ot"> ng-controller=</span><span class="st">&quot;Spreadsheet&quot;</span><span class="ot"> ng-cloak</span><span class="kw">&gt;</span></code></pre>

<p>The <code>ng-app</code> and <code>ng-controller</code> attributes above tell <a href="http://angularjs.org/">AngularJS</a> to call the <code>500lines</code> module’s <code>Spreadsheet</code> function, which would return a <em>model</em>: an object that provides <em>bindings</em> on the document <em>view</em>. (The <code>ng-cloak</code> attribute hides the document from display until the bindings are in place.)</p>

<p>As a concrete example, when the user clicks the <code>&lt;button&gt;</code> defined in the next line, its <code>ng-click</code> attribute will trigger and call <code>reset()</code> and <code>calc()</code>, two named functions provided by the JS model:</p>

<pre class="sourceCode html"><code class="sourceCode html">  <span class="kw">&lt;table&gt;&lt;tr&gt;</span>
    <span class="kw">&lt;th&gt;&lt;button</span><span class="ot"> type=</span><span class="st">&quot;button&quot;</span><span class="ot"> ng-click=</span><span class="st">&quot;reset(); calc()&quot;</span><span class="kw">&gt;</span>↻<span class="er">&lt;</span>/button&gt;<span class="kw">&lt;/th&gt;</span></code></pre>

<p>The next line uses <code>ng-repeat</code> to display the list of column labels on the top row:</p>

<pre class="sourceCode html"><code class="sourceCode html">    <span class="kw">&lt;th</span><span class="ot"> ng-repeat=</span><span class="st">&quot;col in Cols&quot;</span><span class="kw">&gt;</span>{{ col }}<span class="kw">&lt;/th&gt;</span></code></pre>

<p>For example, if the JS model defines <code>Cols</code> as <code>[&quot;A&quot;,&quot;B&quot;,&quot;C&quot;]</code>, then there will be three heading cells (<code>th</code>) labeled accordingly. The <code>{{ col }}</code> notation tells AngularJS to <em>interpolate</em> the expression, filling the contents in each <code>th</code> with the current value of <code>col</code>.</p>

<p>Similarly, the next two lines go through values in <code>Rows</code> — <code>[1,2,3]</code> and so on — creating a row for each one and labeling the leftmost <code>th</code> cell with its number:</p>

<pre class="sourceCode html"><code class="sourceCode html">  <span class="kw">&lt;/tr&gt;&lt;tr</span><span class="ot"> ng-repeat=</span><span class="st">&quot;row in Rows&quot;</span><span class="kw">&gt;</span>
    <span class="kw">&lt;th&gt;</span>{{ row }}<span class="kw">&lt;/th&gt;</span></code></pre>

<p>Because the <code>&lt;tr ng-repeat&gt;</code> tag is not yet closed by <code>&lt;/tr&gt;</code> , the <code>row</code> variable is still available for expressions. The next line creates a data cell (<code>td</code>) in the current row and uses both <code>col</code> and <code>row</code> variables in its <code>ng-class</code> attribute:</p>

<pre class="sourceCode html"><code class="sourceCode html">    <span class="kw">&lt;td</span><span class="ot"> ng-repeat=</span><span class="st">&quot;col in Cols&quot;</span><span class="ot"> ng-class=</span><span class="st">&quot;{ formula: (&#39;=&#39; === sheet[col+row][0]) }&quot;</span><span class="kw">&gt;</span></code></pre>

<p>A few things are going on here. In HTML, the <code>class</code> attribute describes a <em>set of class names</em> that allow CSS to style them differently. The <code>ng-class</code> here evaluates the expression <code>('=' === sheet[col+row][0])</code>; if it is true, then the <code>&lt;td&gt;</code> gets <code>formula</code> as an additional class, which gives the cell a light-blue background as defined in line 8 of <strong>styles.css</strong> with the <code>.formula</code> <em>class selector</em>.</p>

<p>The expression above checks if the current cell is a formula by testing if <code>=</code> is the initial character (<code>[0]</code>) of the string in <code>sheet[col+row]</code>, where <code>sheet</code> is a JS model object with coordinates (such as <code>&quot;E1&quot;</code>) as properties, and cell contents (such as <code>&quot;=A1+C1&quot;</code>) as values. Note that because <code>col</code> is a string and not a number, the <code>+</code> in <code>col+row</code> means concatenation instead of addition.</p>

<p>Inside the <code>&lt;td&gt;</code>, we give the user an input box to edit the cell content stored in <code>sheet[col+row]</code>:</p>

<pre class="sourceCode html"><code class="sourceCode html">       <span class="kw">&lt;input</span><span class="ot"> id=</span><span class="st">&quot;{{ col+row }}&quot;</span><span class="ot"> ng-model=</span><span class="st">&quot;sheet[col+row]&quot;</span><span class="ot"> ng-change=</span><span class="st">&quot;calc()&quot;</span>
<span class="ot">        ng-model-options=</span><span class="st">&quot;{ debounce: 200 }&quot;</span><span class="ot"> ng-keydown=</span><span class="st">&quot;keydown( $event, col, row )&quot;</span><span class="kw">&gt;</span></code></pre>

<p>Here, the key attribute is <code>ng-model</code>, which enables a <em>two-way binding</em> between the JS model and the input box’s editable content. In practice, this means that whenever the user makes a change in the input box, the JS model will update <code>sheet[col+row]</code> to match the content, and trigger its <code>calc()</code> function to recalculate values of all formula cells.</p>

<p>To avoid repeated calls to <code>calc()</code> when the user presses and holds a key, <code>ng-model-options</code> limits the update rate to once every 200 milliseconds.</p>

<p>The <code>id</code> attribute here is interpolated with the coordinate <code>col+row</code>. The <code>id</code> attribute of a HTML element must be different from the <code>id</code> of all other elements in the same document. This ensures that the <code>#A1</code> <em>ID selector</em> refers to a single element, instead of a set of elements like the class selector <code>.formula</code>. When the user presses the <strong>UP</strong>/<strong>DOWN</strong>/<strong>ENTER</strong> keys, the keyboard-navigation logic in <code>keydown()</code> will use ID selectors to determine which input box to focus on.</p>

<p>After the input box, we place a <code>&lt;div&gt;</code> to display the calculated value of the current cell, represented in the JS model by objects <code>errs</code> and <code>vals</code>:</p>

<pre class="sourceCode html"><code class="sourceCode html">      <span class="kw">&lt;div</span><span class="ot"> ng-class=</span><span class="st">&quot;{ error: errs[col+row], text: vals[col+row][0] }&quot;</span><span class="kw">&gt;</span>
        {{ errs[col+row] || vals[col+row] }}<span class="kw">&lt;/div&gt;</span></code></pre>

<p>If an error occurs when computing a formula, the text interpolation uses the error message contained in <code>errs[col+row]</code>, and <code>ng-class</code> applies the <code>error</code> class to the element, allowing CSS to style it differently (with red letters, aligned to the center, etc.).</p>

<p>When there is no error, the <code>vals[col+row]</code> on the right side of <code>||</code> is interpolated instead. If it’s a non-empty string, the initial character (<code>[0]</code>) will evaluate to true, applying the <code>text</code> class to the element that left-aligns the text.</p>

<p>Because empty strings and numeric values have no initial character, <code>ng-class</code> will not assign them any classes, so CSS can style them with right alignment as the default case.</p>

<p>Finally, we close the <code>ng-repeat</code> loop in the column level with <code>&lt;/td&gt;</code>, close the row-level loop with <code>&lt;/tr&gt;</code>, and end the HTML document with:</p>

<pre class="sourceCode html"><code class="sourceCode html">    <span class="kw">&lt;/td&gt;</span>
  <span class="kw">&lt;/tr&gt;&lt;/table&gt;</span>
<span class="kw">&lt;/body&gt;&lt;/html&gt;</span></code></pre>

<h3 id="js-main-controller">JS: Main Controller</h3>

<p>The <code>main.js</code> file defines the <code>500lines</code> module and its <code>Spreadsheet</code> controller function, as required by the <code>&lt;body&gt;</code> element in <code>index.html</code>.</p>

<p>As the bridge between the HTML view and the background worker, it has four tasks:</p>

<ul>
<li>Define the dimensions and labels of columns and rows.</li>
<li>Provide event handlers for keyboard navigation and the reset button.</li>
<li>When the user changes the spreadsheet, send its new content to the worker.</li>
<li>When computed results arrive from the worker, update the view and save the current state.</li>
</ul>

<p>The flowchart in <a href="#figure-19.8">Figure 19.8</a> shows the controller-worker interaction in more detail:</p>

<div class="center figure">
<a name="figure-19.8"></a><img src="spreadsheet-images/00-flowchart.png" alt="Figure 19.8 - Controller-Worker Flowchart" title="Figure 19.8 - Controller-Worker Flowchart" />
</div>

<p class="center figcaption">
<small>Figure 19.8 - Controller-Worker Flowchart</small>
</p>

<p>Now let's walk through the code. In the first line, we request the AngularJS <code>$scope</code>:</p>

<pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="ot">angular</span>.<span class="fu">module</span>(<span class="st">&#39;500lines&#39;</span>, []).<span class="fu">controller</span>(<span class="st">&#39;Spreadsheet&#39;</span>, <span class="kw">function</span> ($scope, $timeout) {</code></pre>

<p>The <code>$</code> in <code>$scope</code> is part of the variable name. Here we also request the <a href="https://docs.angularjs.org/api/ng/service/$timeout"><code>$timeout</code></a> service function from AngularJS; later on, we will use it to prevent infinite-looping formulas.</p>

<p>To put <code>Cols</code> and <code>Rows</code> into the model, simply define them as properties of <code>$scope</code>:</p>

<pre class="sourceCode javascript"><code class="sourceCode javascript">  <span class="co">// Begin of $scope properties; start with the column/row labels</span>
  <span class="ot">$scope</span>.<span class="fu">Cols</span> = [], <span class="ot">$scope</span>.<span class="fu">Rows</span> = [];
  <span class="kw">for</span> (col of <span class="fu">range</span>( <span class="st">&#39;A&#39;</span>, <span class="st">&#39;H&#39;</span> )) { <span class="ot">$scope</span>.<span class="ot">Cols</span>.<span class="fu">push</span>(col); }
  <span class="kw">for</span> (row of <span class="fu">range</span>( <span class="dv">1</span>, <span class="dv">20</span> )) { <span class="ot">$scope</span>.<span class="ot">Rows</span>.<span class="fu">push</span>(row); }</code></pre>

<p>The ES6 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of">for...of</a> syntax makes it easy to loop through ranges with a start and an end point, with the helper function <code>range</code> defined as a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*">generator</a>:</p>

<pre class="sourceCode javascript"><code class="sourceCode javascript">  <span class="kw">function</span>* <span class="fu">range</span>(cur, end) { <span class="kw">while</span> (cur &lt;= end) { <span class="kw">yield</span> cur;</code></pre>

<p>The <code>function*</code> above means that <code>range</code> returns an <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/The_Iterator_protocol">iterator</a>, with a <code>while</code> loop that would <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield"><code>yield</code></a> a single value at a time. Whenever the <code>for</code> loop demands the next value, it will resume execution right after the <code>yield</code> line:</p>

<pre><code>    // If it’s a number, increase it by one; otherwise move to next letter
    cur = (isNaN( cur ) ? String.fromCodePoint( cur.codePointAt()+1 ) : cur+1);
  } }</code></pre>

<p>To generate the next value, we use <code>isNaN</code> to see if <code>cur</code> is meant as a letter (<code>NaN</code> stands for “not a number.”) If so, we get the letter’s <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt">code point value</a>, increment it by one, and <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint">convert the codepoint</a> back to get its next letter. Otherwise, we simply increase the number by one.</p>

<p>Next up, we define the <code>keydown()</code> function that handles keyboard navigation across rows:</p>

<pre class="sourceCode javascript"><code class="sourceCode javascript">  <span class="co">// UP(38) and DOWN(40)/ENTER(13) move focus to the row above (-1) and below (+1).</span>
  <span class="ot">$scope</span>.<span class="fu">keydown</span> = ({which}, col, row)=&gt;{ <span class="kw">switch</span> (which) {</code></pre>

<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/arrow_functions">arrow function</a> receives the arguments <code>($event, col, row)</code> from <code>&lt;input ng-keydown&gt;</code>, using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/New_in_JavaScript/1.7#Pulling_fields_from_objects_passed_as_function_parameter">destructuring assignment</a> to assign <code>$event.which</code> into the <code>which</code> parameter, and checks if it’s among the three navigational key codes:</p>

<pre class="sourceCode javascript"><code class="sourceCode javascript">    <span class="kw">case</span> <span class="dv">38</span>: <span class="kw">case</span> <span class="dv">40</span>: <span class="kw">case</span> <span class="dv">13</span>: <span class="fu">$timeout</span>( ()=&gt;{</code></pre>

<p>If it is, we use <code>$timeout</code> to schedule a focus change after the current <code>ng-keydown</code> and <code>ng-change</code> handler. Because <code>$timeout</code> requires a function as argument, the <code>()=&gt;{…}</code> syntax constructs a function to represent the focus-change logic, which starts by checking the direction of movement:</p>

<pre class="sourceCode javascript"><code class="sourceCode javascript">      <span class="kw">const</span> direction = (which === <span class="dv">38</span>) ? -<span class="dv">1</span> : +<span class="dv">1</span>;</code></pre>

<p>The <code>const</code> declarator means <code>direction</code> will not change during the function’s execution. The direction to move is either upward (<code>-1</code>, from <strong>A2</strong> to <strong>A1</strong>) if the key code is 38 (<strong>UP</strong>), or downward (<code>+1</code>, from <strong>A2</strong> to <strong>A3</strong>) otherwise.</p>

<p>Next up, we retrieve the target element using the ID selector syntax (e.g. <code>&quot;#A3&quot;</code>), constructed with a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/template_strings">template string</a> written in a pair of backticks, concatenating the leading <code>#</code>, the current <code>col</code> and the target <code>row + direction</code>:</p>

<pre class="sourceCode javascript"><code class="sourceCode javascript">      <span class="kw">const</span> cell = <span class="ot">document</span>.<span class="fu">querySelector</span>( `#${ col }${ row + direction }` );
      <span class="kw">if</span> (cell) { <span class="ot">cell</span>.<span class="fu">focus</span>(); }
    } );
  } };</code></pre>

<p>We put an extra check on the result of <code>querySelector</code> because moving upward from <strong>A1</strong> will produce the selector <code>#A0</code>, which has no corresponding element, and so will not trigger a focus change — the same goes for pressing <strong>DOWN</strong> at the bottom row.</p>

<p>Next, we define the <code>reset()</code> function so the reset button can restore the contents of the <code>sheet</code>:</p>

<pre class="sourceCode javascript"><code class="sourceCode javascript">  <span class="co">// Default sheet content, with some data cells and one formula cell.</span>
  <span class="ot">$scope</span>.<span class="fu">reset</span> = ()=&gt;{ 
    <span class="ot">$scope</span>.<span class="fu">sheet</span> = { <span class="dt">A1</span>: <span class="dv">1874</span>, <span class="dt">B1</span>: <span class="st">&#39;+&#39;</span>, <span class="dt">C1</span>: <span class="dv">2046</span>, <span class="dt">D1</span>: <span class="st">&#39;-&gt;&#39;</span>, <span class="dt">E1</span>: <span class="st">&#39;=A1+C1&#39;</span> }; }</code></pre>

<p>The <code>init()</code> function tries restoring the <code>sheet</code> content from its previous state from the <a href="https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Storage#localStorage">localStorage</a>, and defaults to the initial content if it’s our first time running the application:</p>

<pre class="sourceCode javascript"><code class="sourceCode javascript">  <span class="co">// Define the initializer, and immediately call it</span>
  (<span class="ot">$scope</span>.<span class="fu">init</span> = ()=&gt;{
    <span class="co">// Restore the previous .sheet; reset to default if it’s the first run</span>
    <span class="ot">$scope</span>.<span class="fu">sheet</span> = <span class="ot">angular</span>.<span class="fu">fromJson</span>( <span class="ot">localStorage</span>.<span class="fu">getItem</span>( <span class="st">&#39;&#39;</span> ) );
    <span class="kw">if</span> (!<span class="ot">$scope</span>.<span class="fu">sheet</span>) { <span class="ot">$scope</span>.<span class="fu">reset</span>(); }
    <span class="ot">$scope</span>.<span class="fu">worker</span> = <span class="kw">new</span> <span class="fu">Worker</span>( <span class="st">&#39;worker.js&#39;</span> );
  }).<span class="fu">call</span>();</code></pre>

<p>A few things are worth nothing in the <code>init()</code> function above:</p>

<ul>
<li>We use the <code>($scope.init = ()=&gt;{…}).call()</code> syntax to define the function and immediately call it.</li>
<li>Because localStorage only stores strings, we <em>parse</em> the <code>sheet</code> structure from its <a href="https://developer.mozilla.org/en-US/docs/Glossary/JSON">JSON</a> representation using <code>angular.fromJson()</code>.</li>
<li>At the last step of <code>init()</code>, we create a new <a href="https://developer.mozilla.org/en-US/docs/Web/API/Worker">web worker</a> thread and assign it to the <code>worker</code> scope property. Although the worker is not directly used in the view, it’s customary to use <code>$scope</code> to share objects used across model functions, in this case between <code>init()</code> here and <code>calc()</code> below.</li>
</ul>

<p>While <code>sheet</code> holds the user-editable cell content, <code>errs</code> and <code>vals</code> contain the results of calculations — errors and values — that are read-only to the user:</p>

<pre class="sourceCode javascript"><code class="sourceCode javascript">  <span class="co">// Formula cells may produce errors in .errs; normal cell contents are in .vals</span>
  [<span class="ot">$scope</span>.<span class="fu">errs</span>, <span class="ot">$scope</span>.<span class="fu">vals</span>] = [ {}, {} ];</code></pre>

<p>With these properties in place, we can define the <code>calc()</code> function that triggers whenever the user makes a change to <code>sheet</code>:</p>

<pre class="sourceCode javascript"><code class="sourceCode javascript">  <span class="co">// Define the calculation handler; not calling it yet</span>
  <span class="ot">$scope</span>.<span class="fu">calc</span> = ()=&gt;{
    <span class="kw">const</span> json = <span class="ot">angular</span>.<span class="fu">toJson</span>( <span class="ot">$scope</span>.<span class="fu">sheet</span> );</code></pre>

<p>Here we take a snapshot of the state of <code>sheet</code> and store it in the constant <code>json</code>, a JSON string. Next up, we construct a <code>promise</code> from <a href="https://docs.angularjs.org/api/ng/service/$timeout">$timeout</a> that cancels the upcoming computation if it takes more than 99 milliseconds:</p>

<pre class="sourceCode javascript"><code class="sourceCode javascript">    <span class="kw">const</span> promise = <span class="fu">$timeout</span>( ()=&gt;{
      <span class="co">// If the worker has not returned in 99 milliseconds, terminate it</span>
      <span class="ot">$scope</span>.<span class="ot">worker</span>.<span class="fu">terminate</span>();
      <span class="co">// Back up to the previous state and make a new worker</span>
      <span class="ot">$scope</span>.<span class="fu">init</span>();
      <span class="co">// Redo the calculation using the last-known state</span>
      <span class="ot">$scope</span>.<span class="fu">calc</span>();
    }, <span class="dv">99</span> );</code></pre>

<p>Since we made sure that <code>calc()</code> is called at most once every 200 milliseconds via the <code>&lt;input ng-model-options&gt;</code> attribute in HTML, this arrangement leaves 101 milliseconds for <code>init()</code> to restore <code>sheet</code> to the last known-good state and make a new worker.</p>

<p>The worker’s task is to calculate <code>errs</code> and <code>vals</code> from the contents of<code>sheet</code>. Because <strong>main.js</strong> and <strong>worker.js</strong> communicate by message-passing, we need an <code>onmessage</code> handler to receive the results once they are ready:</p>

<pre class="sourceCode javascript"><code class="sourceCode javascript">    <span class="co">// When the worker returns, apply its effect on the scope</span>
    <span class="ot">$scope</span>.<span class="ot">worker</span>.<span class="fu">onmessage</span> = ({data})=&gt;{
      <span class="ot">$timeout</span>.<span class="fu">cancel</span>( promise );
      <span class="ot">localStorage</span>.<span class="fu">setItem</span>( <span class="st">&#39;&#39;</span>, json );
      <span class="fu">$timeout</span>( ()=&gt;{ [<span class="ot">$scope</span>.<span class="fu">errs</span>, <span class="ot">$scope</span>.<span class="fu">vals</span>] = data; } );
    };</code></pre>

<p>If <code>onmessage</code> is called, we know that the <code>sheet</code> snapshot in <code>json</code> is stable (i.e., containing no infinite-looping formulas), so we cancel the 99-millisecond timeout, write the snapshot to localStorage, and schedule a UI update with a <code>$timeout</code> function that updates <code>errs</code> and <code>vals</code> to the user-visible view.</p>

<p>With the handler in place, we can post the state of <code>sheet</code> to the worker, starting its calculation in the background:</p>

<pre class="sourceCode javascript"><code class="sourceCode javascript">    <span class="co">// Post the current sheet content for the worker to process</span>
    <span class="ot">$scope</span>.<span class="ot">worker</span>.<span class="fu">postMessage</span>( <span class="ot">$scope</span>.<span class="fu">sheet</span> );
  };

  <span class="co">// Start calculation when worker is ready</span>
  <span class="ot">$scope</span>.<span class="ot">worker</span>.<span class="fu">onmessage</span> = <span class="ot">$scope</span>.<span class="fu">calc</span>;
  <span class="ot">$scope</span>.<span class="ot">worker</span>.<span class="fu">postMessage</span>( <span class="kw">null</span> );
});</code></pre>

<h3 id="js-background-worker">JS: Background Worker</h3>

<p>There are three reasons for using a web worker to calculate formulas, instead of using the main JS thread for the task:</p>

<ul>
<li>While the worker runs in the background, the user is free to continue interacting with the spreadsheet without getting blocked by computation in the main thread.</li>
<li>Because we accept any JS expression in a formula, the worker provides a <em>sandbox</em> that prevents formulas from interfering with the page that contains them, such as by popping out an <code>alert()</code> dialog box.</li>
<li>A formula can refer to any coordinates as variables. The other coordinates may contain another formula that might end in a cyclic reference. To solve this problem, we use the worker’s <em>global scope</em> object <code>self</code>, and define these variables as <em>getter functions</em> on <code>self</code> to implement the cycle-prevention logic.</li>
</ul>

<p>With these in mind, let’s take a look at the worker’s code.</p>

<p>The worker’s sole purpose is defining its <code>onmessage</code> handler. The handler takes <code>sheet</code>, calculates <code>errs</code> and <code>vals</code>, and posts them back to the main JS thread. We begin by re-initializing the three variables when we receive a message:</p>

<pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="kw">let</span> sheet, errs, vals;
<span class="ot">self</span>.<span class="fu">onmessage</span> = ({data})=&gt;{
  [sheet, errs, vals] = [ data, {}, {} ];</code></pre>

<p>In order to turn coordinates into global variables, we first iterate over each property in <code>sheet</code>, using a <code>for…in</code> loop:</p>

<pre class="sourceCode javascript"><code class="sourceCode javascript">  <span class="kw">for</span> (<span class="kw">const</span> coord <span class="kw">in</span> sheet) {</code></pre>

<p>ES6 introduces <code>const</code> and <code>let</code> declares <em>block scoped</em> constants and variables; <code>const coord</code> above means that functions defined in the loop would capture the value of <code>coord</code> in each iteration.</p>

<p>In contrast, <code>var coord</code> in earlier versions of JS would declare a <em>function scoped</em> variable, and functions defined in each loop iteration would end up pointing to the same <code>coord</code> variable.</p>

<p>Customarily, formula variables are case-insensitive and can optionally have a <code>$</code> prefix. Because JS variables are case-sensitive, we use <code>map</code> to go over the four variable names for the same coordinate:</p>

<pre class="sourceCode javascript"><code class="sourceCode javascript">    <span class="co">// Four variable names pointing to the same coordinate: A1, a1, $A1, $a1</span>
    [ <span class="st">&#39;&#39;</span>, <span class="st">&#39;$&#39;</span> ].<span class="fu">map</span>( p =&gt; [ coord, <span class="ot">coord</span>.<span class="fu">toLowerCase</span>() ].<span class="fu">map</span>(c =&gt; {
      <span class="kw">const</span> name = p+c;</code></pre>

<p>Note the shorthand arrow function syntax above: <code>p =&gt; ...</code> is the same as <code>(p) =&gt; { ... }</code>.</p>

<p>For each variable name, like <code>A1</code> and <code>$a1</code>, we define an <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty">accessor property</a> on <code>self</code> that calculates <code>vals[&quot;A1&quot;]</code> whenever they are evaluated in an expression:</p>

<pre class="sourceCode javascript"><code class="sourceCode javascript">      <span class="co">// Worker is reused across calculations, so only define each variable once</span>
      <span class="kw">if</span> ((<span class="ot">Object</span>.<span class="fu">getOwnPropertyDescriptor</span>( self, name ) || {}).<span class="fu">get</span>) { <span class="kw">return</span>; }

      <span class="co">// Define self[&#39;A1&#39;], which is the same thing as the global variable A1</span>
      <span class="ot">Object</span>.<span class="fu">defineProperty</span>( self, name, { <span class="fu">get</span>() {</code></pre>

<p>The <code>{ get() { … } }</code> syntax above is shorthand for <code>{ get: ()=&gt;{ … } }</code>. Because we define only <code>get</code> and not <code>set</code>, the variables become <em>read-only</em> and cannot be modified from user-supplied formulas.</p>

<p>The <code>get</code> accessor starts by checking <code>vals[coord]</code>, and simply returns it if it’s already calculated:</p>

<pre class="sourceCode javascript"><code class="sourceCode javascript">        <span class="kw">if</span> (coord <span class="kw">in</span> vals) { <span class="kw">return</span> vals[coord]; }</code></pre>

<p>If not, we need to calculate <code>vals[coord]</code> from <code>sheet[coord]</code>.</p>

<p>First we set it to <code>NaN</code>, so self-references like setting <strong>A1</strong> to <code>=A1</code> will end up with <code>NaN</code> instead of an infinite loop:</p>

<pre class="sourceCode javascript"><code class="sourceCode javascript">        vals[coord] = <span class="kw">NaN</span>;</code></pre>

<p>Next we check if <code>sheet[coord]</code> is a number by converting it to numeric with prefix <code>+</code>, assigning the number to <code>x</code>, and comparing its string representation with the original string. If they differ, then we set <code>x</code> to the original string:</p>

<pre class="sourceCode javascript"><code class="sourceCode javascript">        <span class="co">// Turn numeric strings into numbers, so =A1+C1 works when both are numbers</span>
        <span class="kw">let</span> x = +sheet[coord];
        <span class="kw">if</span> (sheet[coord] !== <span class="ot">x</span>.<span class="fu">toString</span>()) { x = sheet[coord]; }</code></pre>

<p>If the initial character of <code>x</code> is <code>=</code>, then it’s a formula cell. We evaluate the part after <code>=</code> with <code>eval.call()</code>, using the first argument <code>null</code> to tell <code>eval</code> to run in the <em>global scope</em>, hiding the <em>lexical scope</em> variables like <code>x</code> and <code>sheet</code> from the evaluation:</p>

<pre class="sourceCode javascript"><code class="sourceCode javascript">        <span class="co">// Evaluate formula cells that begin with =</span>
        <span class="kw">try</span> { vals[coord] = ((<span class="st">&#39;=&#39;</span> === x[<span class="dv">0</span>]) ? <span class="ot">eval</span>.<span class="fu">call</span>( <span class="kw">null</span>, <span class="ot">x</span>.<span class="fu">slice</span>( <span class="dv">1</span> ) ) : x);</code></pre>

<p>If the evaluation succeeds, the result is stored into <code>vals[coord]</code>. For non-formula cells, the value of <code>vals[coord]</code> is simply <code>x</code>, which may be a number or a string.</p>

<p>If <code>eval</code> results in an error, the <code>catch</code> block tests if it’s because the formula refers to an empty cell not yet defined in <code>self</code>:</p>

<pre class="sourceCode javascript"><code class="sourceCode javascript">        } <span class="kw">catch</span> (e) {
          <span class="kw">const</span> match = <span class="ot">/</span><span class="fl">\$?</span><span class="bn">[A-Za-z]</span><span class="fl">+</span><span class="bn">[1-9][0-9]</span><span class="fl">*\b</span><span class="ot">/</span>.<span class="fu">exec</span>( e );
          <span class="kw">if</span> (match &amp;&amp; !( match[<span class="dv">0</span>] <span class="kw">in</span> self )) {</code></pre>

<p>In that case, we set the missing cell’s default value to &quot;0&quot;, clear <code>vals[coord]</code>, and re-run the current computation using <code>self[coord]</code>:</p>

<pre class="sourceCode javascript"><code class="sourceCode javascript">            <span class="co">// The formula refers to a uninitialized cell; set it to 0 and retry</span>
            self[match[<span class="dv">0</span>]] = <span class="dv">0</span>;
            <span class="kw">delete</span> vals[coord];
            <span class="kw">return</span> self[coord];
          }</code></pre>

<p>If the user gives the missing cell a content later on in <code>sheet[coord]</code>, then the temporary value would be overridden by <code>Object.defineProperty</code>.</p>

<p>Other kinds of errors are stored in <code>errs[coord]</code>:</p>

<pre class="sourceCode javascript"><code class="sourceCode javascript">          <span class="co">// Otherwise, stringify the caught exception in the errs object</span>
          errs[coord] = <span class="ot">e</span>.<span class="fu">toString</span>();
        }</code></pre>

<p>In case of errors, the value of <code>vals[coord]</code> will remain <code>NaN</code> because the assignment did not finish executing.</p>

<p>Finally, the <code>get</code> accessor returns the calculated value stored in <code>vals[coord]</code>, which must be a number, a Boolean value, or a string:</p>

<pre class="sourceCode javascript"><code class="sourceCode javascript">        <span class="co">// Turn vals[coord] into a string if it&#39;s not a number or Boolean</span>
        <span class="kw">switch</span> (<span class="kw">typeof</span> vals[coord]) { 
            <span class="kw">case</span> <span class="st">&#39;function&#39;</span>: <span class="kw">case</span> <span class="st">&#39;object&#39;</span>: vals[coord]+=<span class="st">&#39;&#39;</span>; 
        }
        <span class="kw">return</span> vals[coord];
      } } );
    }));
  }</code></pre>

<p>With accessors defined for all coordinates, the worker goes through the coordinates again, invoking each accessor with <code>self[coord]</code>, then posts the resulting <code>errs</code> and <code>vals</code> back to the main JS thread:</p>

<pre class="sourceCode javascript"><code class="sourceCode javascript">  <span class="co">// For each coordinate in the sheet, call the property getter defined above</span>
  <span class="kw">for</span> (<span class="kw">const</span> coord <span class="kw">in</span> sheet) { self[coord]; }
  <span class="kw">return</span> [ errs, vals ];
}</code></pre>

<h3 id="css">CSS</h3>

<p>The <strong>styles.css</strong> file contains just a few selectors and their presentational styles. First, we style the table to merge all cell borders together, leaving no spaces between neighboring cells:</p>

<pre class="sourceCode css"><code class="sourceCode css">table <span class="kw">{</span> <span class="kw">border-collapse:</span> <span class="dt">collapse</span><span class="kw">;</span> <span class="kw">}</span></code></pre>

<p>Both the heading and data cells share the same border style, but we can tell them apart by their background colors: heading cells are light gray, data cells are white by default, and formula cells get a light blue background:</p>

<pre><code>th, td { border: 1px solid #ccc; }
th { background: #ddd; }
td.formula { background: #eef; }</code></pre>

<p>The displayed width is fixed for each cell’s calculated values. Empty cells receive a minimal height, and long lines are clipped with a trailing ellipsis:</p>

<pre class="sourceCode css"><code class="sourceCode css">td div <span class="kw">{</span> <span class="kw">text-align:</span> <span class="dt">right</span><span class="kw">;</span> <span class="kw">width:</span> <span class="dt">120px</span><span class="kw">;</span> <span class="kw">min-height:</span> <span class="dt">1.2em</span><span class="kw">;</span>
         <span class="kw">overflow:</span> <span class="dt">hidden</span><span class="kw">;</span> <span class="kw">text-overflow:</span> <span class="dt">ellipsis</span><span class="kw">;</span> <span class="kw">}</span></code></pre>

<p>The text alignment and decorations are determined by each value’s type, as reflected by the <code>text</code> and <code>error</code> class selectors:</p>

<pre class="sourceCode css"><code class="sourceCode css">div<span class="fl">.text</span> <span class="kw">{</span> <span class="kw">text-align:</span> <span class="dt">left</span><span class="kw">;</span> <span class="kw">}</span>
div<span class="fl">.error</span> <span class="kw">{</span> <span class="kw">text-align:</span> <span class="dt">center</span><span class="kw">;</span> <span class="kw">color:</span> <span class="dt">#800</span><span class="kw">;</span> <span class="kw">font-size:</span> <span class="dt">90%</span><span class="kw">;</span> <span class="kw">border:</span> <span class="dt">solid</span> <span class="dt">1px</span> <span class="dt">#800</span> <span class="kw">}</span></code></pre>

<p>As for the user-editable <code>input</code> box, we use <em>absolute positioning</em> to overlay it on top of its cell, and make it transparent so the underlying <code>div</code> with the cell’s value shows through:</p>

<pre class="sourceCode css"><code class="sourceCode css">input <span class="kw">{</span> <span class="kw">position:</span> <span class="dt">absolute</span><span class="kw">;</span> <span class="kw">border:</span> <span class="dt">0</span><span class="kw">;</span> <span class="kw">padding:</span> <span class="dt">0</span><span class="kw">;</span>
        <span class="kw">width:</span> <span class="dt">120px</span><span class="kw">;</span> <span class="kw">height:</span> <span class="dt">1.3em</span><span class="kw">;</span> <span class="kw">font-size:</span> <span class="dt">100%</span><span class="kw">;</span>
        <span class="kw">color:</span> <span class="dt">transparent</span><span class="kw">;</span> <span class="kw">background:</span> <span class="dt">transparent</span><span class="kw">;</span> <span class="kw">}</span></code></pre>

<p>When the user sets focus on the input box, it springs into the foreground:</p>

<pre class="sourceCode css"><code class="sourceCode css">input<span class="dv">:focus</span> <span class="kw">{</span> <span class="kw">color:</span> <span class="dt">#111</span><span class="kw">;</span> <span class="kw">background:</span> <span class="dt">#efe</span><span class="kw">;</span> <span class="kw">}</span></code></pre>

<p>Furthermore, the underlying <code>div</code> is collapsed into a single line, so it’s completely covered by the input box:</p>

<pre class="sourceCode css"><code class="sourceCode css">input<span class="dv">:focus</span> + div <span class="kw">{</span> <span class="kw">white-space:</span> <span class="dt">nowrap</span><span class="kw">;</span> <span class="kw">}</span></code></pre>

<h2 id="conclusion">Conclusion</h2>

<p>Since this book is <em>500 Lines or Less</em>, a web spreadsheet in 99 lines is a minimal example—please feel free to experiment and extend it in any direction you’d like.</p>

<p>Here are some ideas, all easily reachable in the remaining space of 401 lines:</p>

<ul>
<li>A collaborative online editor using <a href="http://sharejs.org/">ShareJS</a>, <a href="http://angularfire.com">AngularFire</a> or <a href="http://goangular.org/">GoAngular</a>.</li>
<li>Markdown syntax support for text cells, using <a href="http://ngmodules.org/modules/angular-marked">angular-marked</a>.</li>
<li>Common formula functions (<code>SUM</code>, <code>TRIM</code>, etc.) from the <a href="https://en.wikipedia.org/wiki/OpenFormula">OpenFormula standard</a>.</li>
<li>Interoperate with popular spreadsheet formats, such as CSV and SpreadsheetML via <a href="http://sheetjs.com/">SheetJS</a>.</li>
<li>Import from and export to online spreadsheet services, such as Google Spreadsheet and <a href="http://ethercalc.net/">EtherCalc</a>.</li>
</ul>

<h3 id="a-note-on-js-versions">A Note on JS versions</h3>

<p>This chapter aims to demonstrate new concepts in ES6, so we use the <a href="https://github.com/google/traceur-compiler">Traceur compiler</a> to translate source code to ES5 to run on pre-2015 browsers.</p>

<p>If you prefer to work directly with the 2010 edition of JS, the <a href="https://audreyt.github.io/500lines/spreadsheet/as-javascript-1.8.5/">as-javascript-1.8.5</a> directory has <strong>main.js</strong> and <strong>worker.js</strong> written in the style of ES5; the <a href="https://github.com/audreyt/500lines/tree/master/spreadsheet/as-javascript-1.8.5">source code</a> is line-by-line comparable to the ES6 version with the same line count.</p>

<p>For people preferring a cleaner syntax, the <a href="https://audreyt.github.io/500lines/spreadsheet/as-livescript-1.3.0/">as-livescript-1.3.0</a> directory uses <a href="http://livescript.net/">LiveScript</a> instead of ES6 to write <strong>main.ls</strong> and <strong>worker.ls</strong>; it is <a href="https://github.com/audreyt/500lines/tree/master/spreadsheet/as-livescript-1.3.0">20 lines shorter</a> than the JS version.</p>

<p>Building on the LiveScript language, the <a href="https://audreyt.github.io/500lines/spreadsheet/as-react-livescript/">as-react-livescript</a> directory uses the <a href="https://facebook.github.io/react/">ReactJS</a> framework; <a href="https://github.com/audreyt/500lines/tree/master/spreadsheet/as-react-livescript">it is 10 lines more longer</a> than the AngularJS equivalent, but runs considerably faster.</p>

<p>If you are interested in translating this example to alternate JS languages, send a <a href="https://github.com/audreyt/500lines/pulls">pull request</a>—I’d love to hear about it!</p>
  </body>
</html>
